//
//  ViewController.swift
//  MemoryTestSwift
//
//  Created by 柴雪飞 on 2020/8/31.
//  Copyright © 2020 柴雪飞. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    var strs = [String]()
    var strLow  = "0"// 改变后仍然小于16字节
    var strMiddle = "012345678912345"// 15个字节，改变后大于16个字节
    var strHigh = "0123456789123456"// 16个字节
    
    /**
     官方文档关于String、数组、字典等值类型内存优化的提示：
     NOTE
     Collections defined by the standard library like arrays, dictionaries, and strings use an optimization to reduce the performance cost of copying. Instead of making a copy immediately, these collections share the memory where the elements are stored between the original instance and any copies. If one of the copies of the collection is modified, the elements are copied just before the modification. The behavior you see in your code is always as if a copy took place immediately.
     */
    /**
     后边验证了String类型的在大于等于16字节情况下，修改时复制的机制，数组和字典变量占有8字节，查阅博客上，数组和字典虽说是值类型，但变量存储的是指针，实际内容还是在堆上，依旧是引用类型。这个后期还有待证明。
     */
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
//        testAddress()
//        testStringMemonry()
//        string16Explore()
//        testStruct()
//        testClass()
    }
    
    // 1、打印比较类型变量赋值前后地址
    func testAddress() {
        var aInt = 100
        var bInt = aInt
        
        var aDouble = 99.99
        var bDouble = aDouble
        
        var aString = "123456789123456789123456789123456789"
        var bString = aString
        
        var aArrayInt = [1,2,3,4,5,6,7,8,9]
        var bArrayInt = aArrayInt
        
        var aArrayString = ["1","2","3","4","5","6","7","8","9"]
        var bArrayString = aArrayString
        
        var aDictionary = ["key":"value","key1":"value1","key2":"value2","key3":"value3","key4":"value4"]
        var bDictionary = aDictionary
        bDictionary["key5"] = "value5"
        
        print("test Int                 \(getPointer(&aInt))")
        print("test Int                 \(getPointer(&bInt))")
        bInt = 102
        print("bInt改变后的地址           \(getPointer(&bInt))\n")
        
        print("test Double              \(getPointer(&aDouble))")
        print("test Double              \(getPointer(&bDouble))")
        bDouble = 101.99
        print("bDouble改变后的地址        \(getPointer(&bDouble))\n")
        
        print("test String              \(getPointer(&aString))")
        print("test String              \(getPointer(&bString))")
        bString = "123456789123456789123456789123456789+12344444444"
        print("bString改变后的地址        \(getPointer(&bString))\n")
        
        print("test Dictionary          \(getPointer(&aDictionary))")
        print("test Dictionary          \(getPointer(&bDictionary))")
        bDictionary["key5"] = "value5"
        print("bDictionary改变后的地址    \(getPointer(&bDictionary))\n")
        
        print("test Array Int           \(getPointer(&aArrayInt))")
        print("test Array Int           \(getPointer(&bArrayInt))")
        bArrayInt.append(10)
        print("bArrayInt改变后的地址      \(getPointer(&bArrayInt))\n")
        
        print("test Array String        \(getPointer(&aArrayString))")
        print("test Array String        \(getPointer(&bArrayString))")
        bArrayString.append("10")
        print("bArrayString改变后的地址   \(getPointer(&bArrayString))\n")
        
        /**
         1、String占16个字节，其他类型占8个字节；
         2、Array类型在赋值的时候，两个变量指针是同一个，当变量改变内容之后，指针也会改变，其余类型，在赋值新变量的时候，指针就发生了变化。
         3、Array和Dictionary类型的变量都占用8个字节，很明显他的内容并不止于8个字节，猜测：这个虽然是结构体，值类型，但是这8个字节的背后本质上应该还是引用类型，日后继续探索这个。
         */
    }
    // 2、字符串16字大小探索
    func string16Explore() {
        var str1 = "012345678901234" // 15个字节，小于16个字节的情况
        var str2 = str1
        print("str1地址：   \(getPointer(&str1))")
        print("str2地址：   \(getPointer(&str2))")
        
        // 16进制打印2段地址字节数据
//        str1:
//        x/2xg 0x000000016d7ddae8
//        0x16d7ddae8: 0x3736353433323130 0xef34333231303938
        
//        str2:
//        x/2xg 0x000000016d7ddad8
//        0x16d7ddad8: 0x3736353433323130 0xef34333231303938
        
        /**
            在Swift中String占有16个字节空间，如果String的内容小于16字节（=不算），则内容直接存储在这16个字节当中
        上边16进制的30、31、、、即10进制的48、49、、、即0、1、、、的ASCII码。
            最后的0xef：查阅博客，猜测e:可能代表字符串这个类型，f:代表长度。
         */
        str2 = str2 + "123456789" // 大于16个字节的情况
        print("str2地址：   \(getPointer(&str2))")
        
        // 16进制打印地址字节数据
//        str2:大于16字节的情况
//        x/2xg 0x000000016d7ddad8
//        0x16d7ddad8: 0xf000000000000018 0x00000002803927c0
        
        /**
         前8个字节为表示String的字节数，其中16进制的18表示24个字节，正好是str2的字节数，
         后8个字节，查阅博客，表示String内容的真实地址+某个立即数，还有待验证。
         */
        
        // 大于16个字节的情况赋值新变量，探究后8个字节和str2的后8个字节是否一致，如果一直，表示字符串的真正内容是同一份。
        var str3 = str2
        print("str3地址：   \(getPointer(&str3))")
        
        // 16进制打印地址字节数据
//        str3:大于16字节的情况，这16字节的数据和str2的数据完全一样，可以证明，后8个字节的字符串的真实地址指向的是同一份。
//        x/2xg 0x000000016d7dda80
//        0x16d7dda80: 0xf000000000000018 0x00000002803927c0
        
        // 对str3改变一下，猜测结果应该是，str3的后8个字节会变，str2的后8个字节不变，表示，str3的内容在改变的时候，拷贝了一份str2的
        str3.append("1")
        print("str3改变后的地址：   \(getPointer(&str3))")

//        x/2xg 0x000000016d7dda80
//        0x16d7dda80: 0xf000000000000019 0x0000000280392740
        
//        而此时打印的str2的地址和之前的一样，证明，str3的内容改变的时候会拷贝一份str2的内容，并在此作了修改，不影响原str2的内容。
//        前8个字节String字节数变化由之前的16进制的18变为19
    }
    // 3、基于16字节的探索，关于字符串内容大小不同，拷贝的内容不同
    func testStringMemonry() {
        var i = 0
        while i < 1000000 {// 1000000*16B/1024/1024 = 15.26MB 下边未超过16字节的赋值，内存增长大约正是这个值。
            i = i + 1
//            strs.append(strLow + "-\(i)")// 改变字符串 内存：10.8 -> 26
//            strs.append(strLow)// 未改变字符串 内存：10.7 -> 26
            
//            strs.append(strMiddle + "-\(i)")// 改变字符串 内存：10.8 -> 87.3
//            strs.append(strMiddle)// 未改变字符串 内存：10.8 -> 26.2
            
//            strs.append(strHigh + "-\(i)")// 改变字符串 内存：10.8 -> 87.3
//            strs.append(strHigh)// 未改变字符串 内存：10.7 -> 26.1
        }
        /**
         猜测：1、String占用16个字节的空间，其中如果内容大小<16字节，则内容直接存储在String变量的16个字节中，赋值即拷贝；
              2、当String内容超过16字节，(String占用的16个字节的前8个字节表示字符串长度，后8个字节据说表示字符串真实地址+立即数，目前还没打印出来)
                对于这种情况，如果字符串没有改变，则拷贝String变量的16个字节，真正的字符串不会发生拷贝；如果字符串改变，则全拷贝。
         */
    }
    // 4、探索struct
    func testStruct() {
        var demo = structDemo()
        var demo2 = demo
        print("struct demo 地址：  \(getPointer(&demo))")
        print("struct demo2 地址： \(getPointer(&demo2))")
//        demo地址64个字节的内容：
//        (lldb) x/8xg 0x000000016fd25a78
//        0x16fd25a78: 0x00000000656d616e 0xe400000000000000
//        0x16fd25a88: 0xd000000000000027 0x80000001000e94e0
//        0x16fd25a98: 0x0000000281ee5300 0x000000000000000a
//        0x16fd25aa8: 0x00000002820a7720 0x00000002830a4d00
        
//        demo2地址64个字节的内容：
//        (lldb) x/8xg 0x000000016fd25a38
//        0x16fd25a38: 0x00000000656d616e 0xe400000000000000
//        0x16fd25a48: 0xd000000000000027 0x80000001000e94e0
//        0x16fd25a58: 0x0000000281ee5300 0x000000000000000a
//        0x16fd25a68: 0x00000002820a7720 0x00000002830a4d00
        
//        分析：
//        1、struct 中6个属性，其中两个字符串各占16个字节，另外占8个字节，共计64字节；
//        2、打印8段，查看具体内容发现
//            2.1 struct的变量在赋值的时候会全拷贝一份，从demo和demo2两个变量地址差64个字节可以看出；
//            2.2 struct中拷贝的是变量本身占有的字节，（比如字符串变量的16个字节，其余的8个字节），
//                变量之外实际的内容不会拷贝（看demo和demo2的longName后8个字节一致可以验证）
    }
    // 5、探索class
    func testClass() {
        var demo = classDemo()
        var demo2 = demo
        print("class demo 地址：  \(getPointer(&demo))")
        print("class demo2 地址： \(getPointer(&demo2))")
        
//        打印结果，两个地址相差8个字节，引用类型
//        class demo 地址：  0x000000016cf91af0
//        class demo2 地址： 0x000000016cf91ae8
        
//        打印查看8个字节的内容：内容一致，指向同一片空间
//        (lldb) x/xg 0x000000016cf91af0
//        0x16cf91af0: 0x000000028099da00
//        (lldb) x/xg 0x000000016cf91ae8
//        0x16cf91ae8: 0x000000028099da00
        
    }
    //打印内存地址
    func getPointer<T>(_ value: inout T) -> UnsafeRawPointer {
        return withUnsafeMutablePointer(to: &value) { (p) -> UnsafeRawPointer in
            return UnsafeRawPointer(p)
        }
    }
}

struct structDemo {
    var name = "name"
    var longName = "namenamenamenamenanmemanennanenanenanne"
    var classdemo = classDemo()
    var count = 10
    var array = [1,2,3,4,5,6,7,8,9]
    var dic = ["key":"value","key1":"value1","key2":"value2"]
    
}
class classDemo: NSObject {
    var name = "name"
    var longName = "namenamenamenamenanmemanennanenanenanne"
    var count = 10
    var array = [1,2,3,4,5,6,7,8,9]
    var dic = ["key":"value","key1":"value1","key2":"value2"]
}

